package com.meevle.business;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.HibernateException;
import org.red5.core.ApplicationSettings;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.PersistableAttributeStore;
import org.slf4j.Logger;

import com.meevle.dao.SetDAO;
import com.meevle.dao.SlotDAO;
import com.meevle.dao.UserDAO;
import com.meevle.utilities.DaoToVoAdapter;
import com.meevle.utilities.RegexController;
import com.meevle.utilities.VoToDAoAdapter;
import com.meevle.vo.SetVO;
import com.meevle.vo.SlotCommentVO;
import com.meevle.vo.SlotVO;
import com.meevle.vo.UserVO;

public class SetDelegate {

	private static Logger log = Red5LoggerFactory.getLogger(SetDelegate.class,	"Meevle");
	
	/**
	 * Returns all the Tracks of the project
	 * @param voProject the project of which you want all the tracks
	 * @return
	 * @throws IllegalArgumentException if something was wrong with the parameter, the exception message should explain more
	 * @throws Exception If something went wrong during the DB access
	 */
	public List<SlotVO> getSlots(SetVO voProject) throws IllegalArgumentException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(voProject.getName());
			RegexController.getInstance().checkUsername(voProject.getOwner().getUsername());
		} catch (IllegalArgumentException e) {
			throw e;
		}
		
		List<SlotVO> tracks = new ArrayList<SlotVO>();
		
		List<SlotDAO> trackDaos;
		try {
//			trackDaos = ProjectDAO.getTracksByProject(voProject);
			SetDAO p = SetDAO.getSet(voProject);
			trackDaos = p.getSlots();
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		try {
			for(SlotDAO trackDao:trackDaos)
				tracks.add(DaoToVoAdapter.getInstance().getSlotFromSlotDAO(trackDao) );
		} catch (NullPointerException e) {
			log.error(e.getMessage());
		}
		
		return tracks;
	}
	
	/**
	 * Persists an already created Slot. This means that the parameters of this project have already been checked
	 *  and don't need to be checked again. This, contrary to the public persistProject method, that can be called by 
	 *  the client. This method needs to do some checking first.
	 *  
	 * @param project: the project that needs to be persisted
	 * @throws Exception if something went wrong
	 */
	private static boolean persist(SetVO project, UserDAO user) throws Exception
	{
		boolean ret = false;
		
		SetDAO projectDao = new SetDAO();
		projectDao.setName(project.getName());
		projectDao.setOwner(user);
		
		try {
			SetDAO.persist(projectDao);
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
		
		ret = true;
		return true;
	}
	
	/**
	 * Checks if anything is wrong with the user or with the projectVo. More in detail:
	 * <br />
	 * <li>does the user exist in the database;</li>
	 * <li>is there anything that's null;</li>
	 * <li>does the genre of the project exist;</li>
	 * <li>etc.</li>
	 * </ul>
	 * @param user The user that's trying to undertake the action (e.g. inserting a project)
	 * @param projectVo The project that's subject of the action (e.g. the project being inserted)
	 * @return A <code>List<Object></code> that contains a <code>GenreDAO</code> object and a <code>UserDAO</code> object
	 * @throws IllegalArgumentException if something was wrong with the parameters. The Exceptionmessage should say enough.
	 * @throws Exception If something else went wrong
	 */
	private static List<Object> check(UserVO userVo, SetVO projectVo) throws IllegalArgumentException, Exception
	{
		List<Object> objects = null;
		
		try {
			objects = checkParameters(userVo, projectVo);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (NullPointerException e) {
			throw e;
		}

		if( !projectVo.getOwner().equals(userVo) )
			throw new IllegalArgumentException("The owner of the project and the user creating the project are not the same");

		return objects;
		
	}
	
	/**
	 * Adds the project to the database. The code checks if:
	 * - the user, the genre exist in the database
	 * @param setVo the project that the client wants to add to the database.
	 * @throws IllegalArgumentException if something was wrong with the parameter. Read the exception message for more info.
	 * @throws an Exception if something went wrong
	 */
	public static boolean createSet(SetVO setVo)throws IllegalArgumentException, Exception
	{
		Object o = new Object();
		synchronized (o) {
			o.wait (1000);
			}
		System.out.println("\n Set: " + setVo);
		try {
			RegexController.getInstance().checkSetname(setVo.getName());
			System.out.println("\nOwner: " + setVo.getOwner());
			RegexController.getInstance().checkUsername(setVo.getOwner().getUsername());
		} catch (IllegalArgumentException e) {
			throw e;
		}
		
		boolean ret = false;
		
		List<Object> objects = null;
		
			//Check for errors
		try {
			objects = check(new UserVO(setVo.getOwner().getUsername() ), setVo);
		} catch (IllegalArgumentException ill) {
			;
		} catch(Exception e) {
			;
		}
		
		//check if user already owns a set with that name
		try {
			if(UserDelegate.isUserOwnerOfSet(
					DaoToVoAdapter.getInstance().getUserFromUserDAO((UserDAO)objects.get(0))
					, setVo
					)
				)
				throw new IllegalArgumentException("This user already has a project with that name");
		} catch (Exception e) {
			//means it doesn't exist
			;
		}
		
		try{
			if(persist(setVo, UserDAO.getUser(setVo.getOwner() )) )
			{
				UserDAO.addSetToUser(setVo.getOwner(), setVo);
//				createTheSetDir
				ret = createSetDirectory(setVo.getName(), setVo.getOwner().getUsername());
			}
		}
		catch(IllegalArgumentException ill) {	log.error("Adding a new project to the database failed because:\n"
												+ill.getMessage());
//												throw ill;
											}
		
		catch(Exception e)					{	log.error("Adding a new project to the database failed because:\n");
												e.getMessage();
//												throw e;
											}
		

		
		return ret;
	}
	
	private static boolean createSetDirectory(String setName, String userName) {
		boolean ret = new File(ApplicationSettings.getInstance()
					.getContextFullPath()
					+ ApplicationSettings.getInstance().getUserSetPath(userName, setName))
					.mkdirs();
			
			return ret;
	}

	/**
	 * Get all the sets from the database
	 * @return a <code>List</code> of <code>SetVO</code>objects
	 */
	public static List<SetVO> getAllSets()
	{
		List<SetVO> ret = new ArrayList<SetVO>();
		List<SetDAO> projectDaos;
		try {
			projectDaos = SetDAO.getAll();
			for(SetDAO projectDao : projectDaos)
				ret.add(DaoToVoAdapter.getInstance().getSetFromSetDAO(projectDao));
		} catch (Exception e) {
			;
		}
		return ret;
	}
	
	/**
	 * Checks both the parameters of both the <code>UserVO</code> object and the <code>ProjectVO</code> object and returns a 
	 * <code>List</code> of objects containing respectively a GenreDAO object and a UserDAO object
	 * @param userVo The user trying to perform an action (e.g. persisting a track)
	 * @param porjectVo The project undergoing the action (e.g. being persisted)
	 * @return <code>List<Object></code>
	 * @throws IllegalArgumentException If something was wrong with the parameters: the user does not exist, the genre of the track does noet exist 
	 *, ... The Exceptionmessage should say what's wrong
	 * @throws NullPointerException If userVo or projectVo are null
	 * @throws Exception If something went wrong when accessing the DB
	 */
	private static List<Object> checkParameters(UserVO userVo, SetVO projectVo) throws IllegalArgumentException, NullPointerException, Exception
	{
		List<Object> objects;
		List<String> erroneousRequiredParameters;
		
		if(userVo== null || projectVo == null)
			throw new NullPointerException("Nor the User, nor the project can be null");
		
		try {
			objects = checkProjectParameters(projectVo);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		erroneousRequiredParameters = (List<String>) objects.get(1);
		if(erroneousRequiredParameters.size() > 0)
		{
			StringBuffer buf = new StringBuffer();;
			for(String str:erroneousRequiredParameters)
				buf.append(str + "\n");
			throw new IllegalArgumentException(buf.toString());
		}
		
		return objects.subList(0, 0);
	}
	/**
	 * Checks if the required parameters for a project are there
	 * @param projectVo
	 * @return a <code>List<String></code> of <code>Object</code> that contains a <code>GenreDAO</code>object, 
	 * a <code>UserDAO</code>object and a <code>List<String></code> which contains the error messages, if any
	 * @throws IllegalArgumentException if one of the arguments does not exist (user, genre)
	 * @throws Exception If something went wrong when accessing the DB
	 */
	private static List<Object> checkProjectParameters(SetVO projectVo)throws IllegalArgumentException, Exception
	{
		List<Object> ret = new ArrayList<Object>(3);
		List<String> messages = new ArrayList<String>(1);
		
		UserDAO u;
		
		try {
			u = UserDAO.getUser(projectVo.getOwner() );
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		if( projectVo.getName()==null || projectVo.getName().length() == 0)
			messages.add("The name of the project must be set");
		
		ret.add(u);
		ret.add(messages);
		
		return ret;
	}
	
	/**
	 * Get the role the user has in this project
	 * @param user
	 * @param project
	 * @return the role the user has in the specified project. Can be "owner", "participant" or <code>null</code>
	 * @throws IllegalArgumentException if something was wrong with the provided parameters. 
	 * The message should give more info.
	 * @throws Exception If something went wrong when accessing the DB
	 */
	public static String getUserRole(UserVO user, SetVO project) throws IllegalArgumentException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(project.getName());
			RegexController.getInstance().checkUsername(project.getOwner().getUsername());
			RegexController.getInstance().checkUsername(user.getUsername());
		} catch (IllegalArgumentException e) {
			throw e;
		}
		
		String role;
		if(user == null || project == null)
			throw new IllegalArgumentException("Nor the user, nor the project can be null");
		
		try {
			UserDAO.getUser(user);
			SetDAO.getSet(project);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch(Exception e) {
			throw e;
		}
		
		try {
			if(UserDelegate.isUserOwnerOfSet(user, project))
				role = "owner";
			else if(UserDelegate.isUserParticipantInSet(user, project))
				role = "participant";
			else role = null;
		} catch (HibernateException e) {
			log.error("Something went wrong with the DB-access:\n" + e.getMessage());
			e.printStackTrace();
			throw new RuntimeException("Something went wrong while accessing the database. Try again.");
		} catch (Exception e) {
			log.error("Something else went wrong\n" + e.getMessage());
			throw new RuntimeException("Something went wrong, try again");
		}
		
		return role;
	}
	
	/**
	 * Get a <code>List</code> of UserVO objects that participate in a certain project. This includes the owner.
	 * @param projectVo The project you wish to know all the participants of
	 * @return A <code>List</code> of users that all participate to this project
	 * @throws IllegalArgumentException if something was wrong with the parameter. The Exception message should give 
	 * more info
	 * @throws Exception If something went wrong during the DB access
	 */
	public static List<UserVO> getAllParticipantsToSet(SetVO projectVo)throws IllegalArgumentException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(projectVo.getName());
			RegexController.getInstance().checkUsername(projectVo.getOwner().getUsername());
		} catch (IllegalArgumentException e) {
			throw e;
		}
		
		SetDAO project = null;
		List<UserVO> participants = new ArrayList<UserVO>();
		List<SlotDAO> projectTracks;
		
		try {
			project = SetDAO.getSet(projectVo);
			projectTracks = project.getSlots();
			
			for(SlotDAO trackDao : projectTracks)
				participants.add(
						DaoToVoAdapter.getInstance().getUserFromUserDAO( trackDao.getOwner() )
						);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (NullPointerException e) {
			//trackDao.getOwner() = null
			;
		} catch (Exception e) {
			throw e;
		}
		
		return participants;
	}
	
	/**
	 * Get all sets with a certain name
	 * @param setname The name of the sets
	 * @return A <code>List</code> of all sets with that particular name
	 * @throws HibernateException When something went wrong during the database access
	 * @throws Exception When something else than the databaseaccess went wrong
	 */
	public static List<SetVO> getAllSetsByName(String projectname) throws HibernateException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(projectname);
		} catch (IllegalArgumentException e) {
			throw e;
		}
		
		List<SetVO>projects = new ArrayList<SetVO>();
		List<SetDAO> projectDaos;
		try {
			projectDaos = SetDAO.getAllSetsByName(projectname);
		} catch (HibernateException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		for(SetDAO projectDao : projectDaos)
			projects.add(DaoToVoAdapter.getInstance().getSetFromSetDAO(projectDao));
		
		return projects;
	}
	
	public static boolean addSlot(SetVO projectVo, SlotVO trackVo) throws IllegalArgumentException, Exception
	{
		boolean ret = true;
		try {
			RegexController.getInstance().checkSetname(projectVo.getName());
			RegexController.getInstance().checkSlotname(trackVo.getName());
			RegexController.getInstance().checkUsername(trackVo.getOwner().getUsername());
			RegexController.getInstance().checkUsername(projectVo.getOwner().getUsername());
			
				//Do the user, the project and the track even exist?
			SlotDAO.getSlot(trackVo);
			SetDAO.getSet(projectVo);
			
			
			
				//if all of the above checks went well, we can add the track to the project
			SetDAO projectDao = SetDAO.getSet(projectVo);
			SlotDAO trackDao = SlotDAO.getSlot(trackVo);
			
				//only add the track if if doesn't already exist in the project
			if(!projectDao.getSlots().contains(trackDao))
			{
				projectDao.getSlots().add(trackDao);
				
				PersistenceManagerImpl.merge(projectDao);
			}
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e)
		{
			throw e;
		}
		
		return ret;
	}
	
	/**
	 * Add a track to a project.
	 * @param projectVo The project you're adding the track to
	 * @param trackVo The track you're adding to the project
	 * @throws IllegalArgumentException If there was something wrong with the arguments
	 * @throws Exception 
	 */
	public static boolean addSlot(UserVO userVo, SetVO projectVo, SlotVO trackVo) throws IllegalArgumentException, Exception
	{
		boolean ret = true;
		try {
			RegexController.getInstance().checkSetname(projectVo.getName());
			RegexController.getInstance().checkSlotname(trackVo.getName());
			RegexController.getInstance().checkUsername(trackVo.getOwner().getUsername());
			RegexController.getInstance().checkUsername(projectVo.getOwner().getUsername());
			RegexController.getInstance().checkUsername(userVo.getUsername());
			
				//Do the user, the project and the track even exist?
			UserDAO.getUser(userVo);
			SlotDAO.getSlot(trackVo);
			SetDAO.getSet(projectVo);
			
			
			
				//if all of the above checks went well, we can add the track to the project
			SetDAO projectDao = SetDAO.getSet(projectVo);
			SlotDAO trackDao = SlotDAO.getSlot(trackVo);
			
				//only add the track if if doesn't already exist in the project
			if(!projectDao.getSlots().contains(trackDao))
			{
				projectDao.getSlots().add(trackDao);
				
				PersistenceManagerImpl.merge(projectDao);
			}
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e)
		{
			throw e;
		}
		
		return ret;
	}
	
	/**
	 * Add slots to a set.
	 * @param userVo The use that's adding the slots to the set
	 * @param projectVo The set you're adding the slots to
	 * @param trackVos The slots you're adding to the set
	 * @return True if all went well, false if there was a problem
	 * @throws IllegalArgumentException If there was something wrong with the arguments
	 * @throws Exception If something went wrong during the DB access
	 */
	public static boolean addSlots(UserVO userVo, SetVO projectVo, List<SlotVO> trackVos) throws IllegalArgumentException, Exception
	{
		boolean ret = true;
		int size = trackVos.size();
		try {
			for(int i=0; i < size && ret; i++)
			{
				if(!addSlot(userVo, projectVo, trackVos.get(i)))
					ret = false;
			}
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		return ret;
	}

	/**
	 * Get all the slots of a certain set
	 * @param projectVo
	 * @return
	 * @throws IllegalArgumentException
	 * @throws Exception
	 */
	public static List<SlotVO> getAllSlots(SetVO projectVo)throws IllegalArgumentException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(projectVo.getName());
			RegexController.getInstance().checkUsername(projectVo.getOwner().getUsername());
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw e;
		}
		
		List<SlotDAO> trackDaos = null;
		List<SlotVO> trackVos = new ArrayList<SlotVO>();
		try {
			SetDAO pr = SetDAO.getSet(projectVo);
//			trackDaos = ProjectDAO.getTracksByProject(projectVo);
			trackDaos = pr.getSlots();
			for(SlotDAO trackDao : trackDaos)
				trackVos.add(DaoToVoAdapter.getInstance().getSlotFromSlotDAO(trackDao));
			
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
		return trackVos;
	}
	
	public static List<SetVO> getAllSetsByUser(UserVO ownerVo)throws IllegalArgumentException, Exception
	{
		List<SetDAO> setDaos = null;
		List<SetVO> sets = new ArrayList<SetVO>(0);
		try {
			setDaos = SetDAO.getAllSetsByUser(ownerVo);

			for(SetDAO set : setDaos)
				sets.add(DaoToVoAdapter.getInstance().getSetFromSetDAO(set));
			
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
		return sets;
	}
	
	/**
	 * Update a set with new values
	 * @param oldProject
	 * @param newProject
	 * @throws IllegalArgumentException If there was something wrong with the provided parameters
	 * @throws Exception If there went something wrong during the DB access
	 */
	public static void update(SetVO oldProject, SetVO newProject)throws IllegalArgumentException, Exception
	{
		try {
			RegexController.getInstance().checkSetname(oldProject.getName());
			RegexController.getInstance().checkSetname(newProject.getName());
			RegexController.getInstance().checkUsername(oldProject.getOwner().getUsername());
			RegexController.getInstance().checkUsername(newProject.getOwner().getUsername());
			
			SetDAO.update(oldProject, newProject);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
	}
	
	public static void removeSlotFromSet(SlotVO slotVo, SetVO setVo) {
		SetDAO set = null;
		SlotDAO slot = null;
		try {
			set = SetDAO.getSet(setVo);
			slot = SlotDAO.getSlot(slotVo);
			set.getSlots().remove(slot);
			PersistenceManagerImpl.merge(set);
			
			PersistenceManagerImpl.deleteSlot(slotVo);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
     * Get a subset of all the sets
     *
     * @param startIndex
     *            Index of the first set
     * @param numberOfItems
     *            Number of sets that being returned
     * @return A <code>List</code> of <code>SetDAO</code> objects
     * @throws HibernateException
     *             When something went wrong during the database access
     * @throws Exception
     *             When something else than the databaseaccess went wrong
     */
    public static List<SetVO> getAllSetsFromTo(int startIndex,
                    int numberOfItems, String howToOrder) throws HibernateException,
                    Exception {

            List<SetVO> sets = new ArrayList<SetVO>();

            List<SetDAO> setDaos;
            try {
                    setDaos = SetDAO.getAllSetsFromTo(startIndex, numberOfItems,
                                    howToOrder);
            } catch (HibernateException e) {
                    throw e;
            } catch (Exception e) {
                    throw e;
            }

            for (SetDAO setDao : setDaos)
                    sets.add(DaoToVoAdapter.getInstance().getSetFromSetDAO(
                                    setDao));

            return sets;
    }

	public static double calculateAverageRating(SetVO setVo) {
		double average = 0;
		
		List<SlotVO> slots;
		try {
			slots = SetDelegate.getAllSlots(setVo);
			if (slots.size() > 0) {
				double noemerSlots = slots.size();
				double sumAverageSlots = 0;
				for (SlotVO slot : slots) {
					List<SlotCommentVO> comments = SlotDelegate
							.getSlotComments(slot);
					if (comments.size() > 0) {
						double sumSlot = 0;
						for (SlotCommentVO comment : comments) {
							sumSlot += comment.getRating();
						}

						sumAverageSlots += sumSlot / comments.size();
					}
				}// end loop over all the slots of a set
				average = sumAverageSlots / slots.size();
			}
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return average;
	}
	
}
